#include <RenderSubSystem/Renderinglayer.h>
#include <RenderSubSystem/DrawlistNode.h>
#include <RenderSubSystem/Renderable.h>
#include <RenderSubSystem/CollectionNode.h>
#include <algorithm>

namespace cvRenderInterface
{

	Renderinglayer::Renderinglayer() : need_reordering( false )
	{}

	Renderinglayer::~Renderinglayer()
	{
		destroy();
	}

	void Renderinglayer::destroy()
	{
		LOGDEBUG("::RENDER(): DESTROYING LAYER");
		renderList.clear_and_dispose(renderinglist_element_disposer());
	}

	void Renderinglayer::triggerZSort()
	{
		need_reordering = true;
	}

	void Renderinglayer::processZSort()
	{
		// TODO: use best algorithm/method.
		if( need_reordering )		// only sort when requested.
		{

			// sort main rendering list.
			renderList.sort( compare_drawnodes );

			// sort collections. The sort() operation may split the collection
			// into bits, so #4 processCollections() stage may re-assemble the collections
			// taking the new limitation into account.
			bool sort_done = true;
			do
			{
				sort_done = true;
				for( renderinglist_it_t it = renderList.begin(); it != renderList.end(); ++it )
				{
					if(it->isCollection())
					{
						if(static_cast<CollectionNode&>(*it).sort())
						{
							// sort new nodes into correct places and try again.
							renderList.sort( compare_drawnodes );
							sort_done = false;
							break;
						}
					}
				}
			} while(!sort_done);

			need_reordering = false;
		}
	}

	/**
	 *	Searches for nodes in the rendering list which could form an collection.
	 */
	/// @begin where we start looking.
	/// @end end of the sequence when function returns true, otherwise set as ++begin
	/// @return true, if an suitable range was found.  range is [begin, end[
	bool Renderinglayer::search_collection_range
	( const renderinglist_it_t & begin, renderinglist_it_t & end ) const
	{
		end = begin;
		++end;
		if( end != renderList.end() && node_qualifiable(begin, end))
		{
			do
			{
				++end;
				if(end == renderList.end())	{ break; }
			} while(node_qualifiable(begin, end));
			return true;
		}
		else
		{
			return false;
		}
	}

	inline bool Renderinglayer::node_qualifiable
	( const renderinglist_it_t & ait, const renderinglist_it_t & bit)
	{
		return ((ait->collectionType() == bit->collectionType()) && (ait->drawGroup() == 0 || bit->drawGroup() == 0 ||
				(ait->drawGroup() == bit->drawGroup())) );
	}

	renderinglist_it_t
	Renderinglayer::get_collection_from_range
	( renderinglist_it_t it, const renderinglist_it_t & end )
	{
		renderinglist_it_t node = end;
		size_t batchsz = 0;
		while( it != end )
		{
			if(it->isCollection() )
			{
				CollectionNode &  batch = static_cast<CollectionNode&>( *it );
				if( batch.getNodes().size() > batchsz )
				{
					batchsz = batch.getNodes().size();
					node = it;
				}
			}
			++it;
		}
		return node;
	}

	void Renderinglayer::processCollections()
	{
		renderinglist_it_t it = renderList.begin();
		renderinglist_it_t range_end;


		/** Manage renderinglist. */
		while( it != renderList.end() )
		{	/// search for a range
			if( search_collection_range( it, range_end ) )
			{	/** Combine range into single CollectionNode. */
				renderinglist_it_t bit;
				CollectionNode * batch = 0;

				/// Search for allredy existing collection in the range.
				bit = get_collection_from_range( it, range_end );

				if( bit == range_end )
				{
					/// Couldn't find any, request new collection.
					batch = static_cast<Renderable&>( *it ).createCollection();
					LOGDEBUG("::RENDER()#4: Fetching nodes to fresh CollectionNode");
				}
				else
				{
					/// Found collection, update-insert nodes.
					batch = &static_cast<CollectionNode&>( *bit );
					// remove found collection temporaly from range:
					if( bit == it )
					{
						it = renderList.erase( bit );
					}
					else
					{
						renderList.erase( bit );
					}
				}

				// its possible that Renderable does not even have responding CollectionNode. (eg. RenderableTilemap)
				if( batch )
				{
					// CollectionNode isa able to work out how to handle the nodes.
					batch->fetchNodes( it, range_end );		// fetch range [it, range_end) into CollectionNode
					renderList.insert( range_end, *batch );	// insert CollectionNode into main list
				}
			}
			it = range_end;
		}
	}

	void Renderinglayer::lockLayer(bool enable)
	{
		for( renderinglist_it_t it = renderList.begin(); it != renderList.end(); ++it )
		{
			it->lockTransfer(enable);
		}
	}

};
